package.path = "./plugin/Q3761-1376/?.lua;"
require "utils"
require "json"
local math = require "math"

-- Q/GDW 1376.1 协议插件
-- 根据3761-BY.md文档实现

-- 常量定义---------------------------------------------------------------------------------------------------------------
local FRAME_START = 0x68
local FRAME_END = 0x16

-- AFN 应用功能码定义
local AFN = {
    CONFIRM_DENY = 0x00,    -- 确认/否认
    RESET = 0x01,           -- 复位
    LINK_TEST = 0x02,       -- 链路接口检测
    SET_PARAM = 0x04,       -- 设置参数
    REQ_CONFIG = 0x09,      -- 请求终端配置及信息
    READ_PARAM = 0x0A,      -- 读取参数
    DATA_FORWARD = 0x10     -- 数据转发
}

-- 功能码定义 (PRM=1时，主站发送)
local FUNC_CODE = {
    RESET = 1,              -- 复位命令
    USER_DATA = 4,          -- 用户数据
    LINK_TEST = 9,          -- 链路测试
    REQ_1_DATA = 10,        -- 请求1级数据
    REQ_2_DATA = 11         -- 请求2级数据
}

-- 功能码定义 (PRM=0时，终端响应)
local RESP_CODE = {
    ACK = 0,                -- 认可
    USER_DATA = 8,          -- 用户数据
    DENY = 9,               -- 否认
    LINK_STATUS = 11        -- 链路状态
}

-- 全局变量
rxBuf = {}
local frameSeq = 0

-- 工具函数---------------------------------------------------------------------------------------------------------------

-- 计算校验和
function checkSum(buffer, startIndex, endIndex)
    local sum = 0
    for i = startIndex, endIndex do
        sum = sum + (buffer[i] or 0)
    end
    return sum % 256
end

-- 16进制转10进制
function hexToDec(hex)
    local dec = 0
    local len = string.len(hex)
    for i = 1, len do
        dec = dec + tonumber(string.sub(hex, len - i + 1, len - i + 1), 16) * 2 ^ ((i - 1) * 4)
    end
    return dec
end

-- BCD码转十进制
function bcdToDec(bcd)
    return math.floor(bcd / 16) * 10 + (bcd % 16)
end

-- 十进制转BCD码
function decToBcd(dec)
    return math.floor(dec / 10) * 16 + (dec % 10)
end

function convertDlt645To3761(dlt645Address)
    -- 确保输入地址长度正确
    if string.len(dlt645Address) ~= 12 then
        return nil, "Invalid DLT645 address length"
    end

    -- 分解DLT645地址
    local A6 = string.sub(dlt645Address, 1, 2)
    local A5 = string.sub(dlt645Address, 3, 4)
    local A4 = string.sub(dlt645Address, 5, 6)
    local A3 = string.sub(dlt645Address, 7, 8)
    local A2 = string.sub(dlt645Address, 9, 10)
    local A1 = string.sub(dlt645Address, 11, 12)

    -- 计算A2*100 + A1 + 0xA000
    local highPart = tonumber(A2) * 100 + tonumber(A1) + 0xA000

    -- 组合A4A3和计算得到的高位部分
    local resultAddress = string.format("%s%s%05d", A4, A3, highPart)

    return resultAddress
end

-- 解析BCD编码的电能值（4字节，小数点后2位）
function parseBCDEnergyValue(valueBytes)
    if not valueBytes or #valueBytes < 4 then
        return 0
    end
    
    local result = 0
    local factor = 0.01  -- 从小数点后2位开始
    
    for i = 1, 4 do
        local byte = valueBytes[i] or 0
        local lowDigit = byte % 16      -- 低4位
        local highDigit = math.floor(byte / 16)  -- 高4位
        
        -- 验证BCD编码有效性
        if lowDigit > 9 or highDigit > 9 then
            -- print("Q3761-1376: BCD解码错误: " .. string.format("0x%02X", byte))
            return 0  -- 解码失败返回0
        end
        
        -- BCD解码：低位在前，高位在后
        result = result + lowDigit * factor
        factor = factor * 10
        result = result + highDigit * factor
        factor = factor * 10
    end
    
    
    -- 适应Go代码的decimal处理逻辑，返回乘以100的整数值
    -- Go代码会除以10^decimal(2) = 100，所以这里先乘以100
    local finalResult = result * 100
    
    return finalResult
end

-- 地址转换
function convertAddress(frame, address)
    if not address or #address ~= 9 then
        -- 默认地址示例: 41544924
        frame[8] = 0x41
        frame[9] = 0x54
        frame[10] = 0x49
        frame[11] = 0x24
        frame[12] = 0x00
        return
    end
    
    -- 解析9位地址字符串: "053040961"
    local digits = {}
    for i = 1, 9 do
        digits[i] = tonumber(string.sub(address, i, i)) or 0
    end
    
    -- 前4位作为行政区划码 (BCD编码): 0530
    local region_d1 = digits[1]  -- 0
    local region_d2 = digits[2]  -- 5  
    local region_d3 = digits[3]  -- 3
    local region_d4 = digits[4]  -- 0
    
    -- BCD编码：每字节两个十进制数字，高4位是十位，低4位是个位
    -- 小端传输：低字节在前
    frame[8] = (region_d3 * 16) + region_d4   -- 低字节: 30H (3在高4位，0在低4位)
    frame[9] = (region_d1 * 16) + region_d2   -- 高字节: 05H (0在高4位，5在低4位)
    
    -- 后5位作为终端地址 (二进制编码): 40961  
    local terminal = digits[5] * 10000 + digits[6] * 1000 + digits[7] * 100 + digits[8] * 10 + digits[9]
    frame[10] = terminal % 256         -- 终端地址低字节
    frame[11] = math.floor(terminal / 256) % 256  -- 终端地址高字节
    
    frame[12] = 0xDE  -- 主站地址
end

-- 生成当前时间 (BCD格式)
function getCurrentTime()
    local now = os.date("*t")
    return {
        decToBcd(now.sec),   -- 秒
        decToBcd(now.min),   -- 分
        decToBcd(now.hour),  -- 时
        decToBcd(now.day),   -- 日
        decToBcd(now.month) + ((now.wday - 1) * 32),  -- 月+星期
        decToBcd(now.year % 100)  -- 年
    }
end

-- 帧序列号管理
function getNextSeq()
    frameSeq = (frameSeq + 1) % 16
    return frameSeq
end

-- 重置帧序号（用于测试）
function resetFrameSeq()
    frameSeq = 0
end

-- 构建基本帧结构
function buildFrame(address, afn, fn, pn, data, isUplink)
    local frame = {}
    local dataLen = data and #data or 0
    
    -- 帧头1
    frame[1] = FRAME_START
    
    -- 计算用户数据区长度：控制域(1) + 地址域(5) + AFN(1) + SEQ(1) + DA(2) + DT(2) + 数据域长度
    local userDataLen = 1 + 5 + 1 + 1 + 2 + 2 + dataLen  -- 总共13字节固定 + 数据长度
    
    -- 长度域：按照Q/GDW 1376.1协议，长度域L = 用户数据区长度*4 + 2
    local lengthField = userDataLen * 4 + 2
    frame[2] = lengthField % 256
    frame[3] = math.floor(lengthField / 256) % 256
    frame[4] = frame[2]  -- 重复
    frame[5] = frame[3]  -- 重复
    
    -- 帧头2
    frame[6] = FRAME_START
    
    -- 控制域C 
    if isUplink then
        -- 上行帧：DIR=1, PRM=1, 功能码=9(链路测试)
        frame[7] = 0xC9  -- 11001001B
    else
        -- 下行帧：DIR=0, PRM=1, 功能码=1010B(请求1级数据)
        frame[7] = 0x4A  -- 01001010B
    end
    
    -- 地址域A (5字节)
    convertAddress(frame, address)
    
    -- 应用功能码AFN
    frame[13] = afn
    
    -- 帧序列域SEQ
    if isUplink then
        -- 上行帧：FIR=1, FIN=1, CON=1, PSEQ=7 (需要确认)
        frame[14] = 0x77  -- 01110111B
    else
        -- 下行帧：FIR=0, FIN=1, CON=1, PSEQ=0 (按照正确报文)
        frame[14] = 0x70  -- 01110000B
    end
    
    -- 数据单元标识 DA (2字节)
    frame[15] = pn % 256          -- DA1
    frame[16] = math.floor(pn / 256) % 256  -- DA2
    
    -- 数据单元标识 DT (2字节)  
    frame[17] = fn % 256          -- DT1
    frame[18] = math.floor(fn / 256) % 256  -- DT2
    
    -- 数据内容
    local dataIndex = 19
    if data then
        for i = 1, #data do
            frame[dataIndex] = data[i]
            dataIndex = dataIndex + 1
        end
    end
    
    -- 校验和
    frame[dataIndex] = checkSum(frame, 7, dataIndex - 1)
    dataIndex = dataIndex + 1
    
    -- 结束符
    frame[dataIndex] = FRAME_END
    
    return frame
end

-- 协议命令生成函数---------------------------------------------------------------------------------------------------------------

-- 生成登录命令 (AFN=02H, F1, p0)
function generateLoginCommand(address)
    return buildFrame(address, AFN.LINK_TEST, 1, 0, nil, true)  -- 上行帧
end

-- 生成心跳命令 (AFN=02H, F3, p0)
function generateHeartbeatCommand(address)
    local timeData = getCurrentTime()
    return buildFrame(address, AFN.LINK_TEST, 4, 0, timeData, true)  -- 上行帧
end

-- 生成退出登录命令 (AFN=02H, F2, p0)
function generateLogoutCommand(address)
    return buildFrame(address, AFN.LINK_TEST, 2, 0, nil, true)  -- 上行帧
end

-- 生成透明转发命令 (AFN=10H, F1, p0)
function generateTransparentForward(address, port, timeout, data, cmdName)
    local forwardData = {}
    -- 针对 dev_consumption 指令特殊处理参数
    if cmdName == "dev_consumption" then
        table.insert(forwardData, port or 1)
        table.insert(forwardData, 0x68)  -- 控制字，修改为0x68 (9600-E-8-1)
        table.insert(forwardData, 0x7F)  -- 超时时间1
        table.insert(forwardData, 0xC8)  -- 超时时间2
        table.insert(forwardData, 0x14)  -- 正确报文为 0x14
        table.insert(forwardData, 0x00)
        
        -- 打印透明转发参数
    else
        -- 原有逻辑
        table.insert(forwardData, port or 1)
        table.insert(forwardData, 0x6B)
        table.insert(forwardData, 0x7F)
        table.insert(forwardData, 0xC8)
        table.insert(forwardData, 0x20)
        table.insert(forwardData, 0x00)
    end
    if data then
        for i = 1, #data do
            table.insert(forwardData, data[i])
        end
    end
    for i = 1, 16 do
        table.insert(forwardData, 0x00)
    end
    
    local cmd = buildFrame(address, AFN.DATA_FORWARD, 1, 0, forwardData, false)
    
    -- 打印生成的透明转发帧内容
    local hexStr = ""
    for i = 1, #cmd do
        hexStr = hexStr .. string.format("%02X ", cmd[i])
    end
    
    return cmd
end

-- 生成读取终端版本信息命令 (AFN=09H, F1, p0)
function generateReadVersionCommand(address)
    return buildFrame(address, AFN.REQ_CONFIG, 1, 0, nil, false)  -- 下行帧
end

-- 生成读取通信模块版本信息命令 (AFN=09H, F9, p0)
function generateReadModuleVersionCommand(address)
    return buildFrame(address, AFN.REQ_CONFIG, 256, 0, nil, false)  -- 下行帧，F9对应bit8，即256
end

-- 数据解析函数---------------------------------------------------------------------------------------------------------------

-- 将DT编码转换为实际的Fn值
function convertDTToFn(dtValue)
    -- DT编码格式：DT2*256 + DT1
    -- 根据3761协议，DT1对位表示某一信息类组的1~8种信息类型
    -- DT2表示信息类组
    local dt1 = dtValue % 256  -- DT1
    local dt2 = math.floor(dtValue / 256)  -- DT2
    
    -- 查找DT1中设置的位（使用Lua兼容的位运算）
    for bit = 0, 7 do
        local bitMask = 2^bit  -- 等同于 (1 << bit)
        if utils.And(dt1, bitMask) ~= 0 then
            -- 计算实际的Fn值：组号*8 + 位位置 + 1
            return dt2 * 8 + bit + 1
        end
    end
    
    return 0  -- 未找到设置的位
end

-- 解析接收帧
function parseFrame(buffer, bufLen)
    if bufLen < 16 then
        return nil, "帧长度不足"
    end
    
    -- 查找帧头
    local startIndex = 1
    for i = 1, bufLen - 15 do
        if buffer[i] == FRAME_START and i + 5 <= bufLen and buffer[i + 5] == FRAME_START then
            startIndex = i
            break
        end
    end
    
    if startIndex >= bufLen - 15 then
        return nil, "未找到有效帧头"
    end
    
    -- 提取长度
    local len1 = buffer[startIndex + 1] or 0
    local len2 = buffer[startIndex + 2] or 0
    local frameLen = math.floor(((len2 * 256 + len1) - 2) / 4)  -- 用户数据长度
    
    -- 完整帧长度 = 起始符(1) + 长度域(4) + 起始符(1) + 用户数据区(frameLen) + 校验(1) + 结束符(1) = frameLen + 8
    local totalFrameLen = startIndex + frameLen + 7  -- 修正：应该是+7而不是+8
    if totalFrameLen > bufLen then
        return nil, "帧不完整，预期长度: " .. totalFrameLen .. ", 实际长度: " .. bufLen
    end
    
    -- 校验和验证 (从控制域到数据区末尾)
    local calcCS = checkSum(buffer, startIndex + 6, startIndex + frameLen + 5)
    local frameCS = buffer[startIndex + frameLen + 6] or 0
    
    if calcCS ~= frameCS then
        return nil, "校验和错误，计算值: " .. calcCS .. ", 帧值: " .. frameCS
    end
    
    -- 解析帧内容
    local frame = {
        startIndex = startIndex,
        totalLen = frameLen + 9,
        ctrl = buffer[startIndex + 6] or 0,
        address = (buffer[startIndex + 9] or 0) * 256 + (buffer[startIndex + 10] or 0),  -- 修正：终端地址在地址域的第3-4字节
        masterAddr = buffer[startIndex + 11] or 0,  -- 修正：主站地址在地址域的第5字节
        afn = buffer[startIndex + 12] or 0,  -- 修正：AFN位置
        seq = buffer[startIndex + 13] or 0,  -- 修正：SEQ位置
        dataStart = startIndex + 14,  -- 修正：数据起始位置
        dataLen = frameLen - 8,
        checksum = frameCS
    }
    
    return frame, "成功"
end

-- 解析数据单元标识
function parseDataUnitID(frame, buffer)
    if frame.dataLen < 4 then
        return nil, "数据长度不足"
    end
    
    local da1 = buffer[frame.dataStart] or 0
    local da2 = buffer[frame.dataStart + 1] or 0
    local dt1 = buffer[frame.dataStart + 2] or 0
    local dt2 = buffer[frame.dataStart + 3] or 0
    
    local pn = da2 * 256 + da1
    local fn = dt2 * 256 + dt1
    
    return {
        pn = pn,
        fn = fn,
        dataStart = frame.dataStart + 4
    }
end

-- 主要接口函数---------------------------------------------------------------------------------------------------------------

-- 设备自定义命令
function DeviceCustomCmd(sAddr, cmdName, cmdParam, step)
    local params = {}
    if cmdParam and cmdParam ~= "" then
        local success, result = pcall(json.jsondecode, cmdParam)
        if success and result and type(result) == "table" then
            params = result
        end
    else
    end
    
    -- 解析设备地址，支持多种格式
    local commAddr, meterAddr = parseDeviceAddress(sAddr)
    
    -- 如果commandParam中提供了地址，优先使用commandParam中的地址
    if params and params.commAddr then
        commAddr = params.commAddr
    end
    if params and params.meterAddr then
        meterAddr = params.meterAddr
    end
    
    local password = (params and params.password) or "000000"  -- 操作密码
    
    if cmdName == "dev_consumption" then
        -- 正向总有功电能 DI = 00 00 01 00
        -- 尝试使用组合有功总电能 DI = 00 00 00 00
        local dltCmd = generateDLT645ReadCommand(meterAddr, {0x00, 0x00, 0x00, 0x00})
        
        
        local cmd = generateTransparentForward(commAddr, 1, 30, dltCmd, "dev_consumption")
        return {Status = "0", Variable = cmd}
        
    elseif cmdName == "OpenValve" then
        -- 跳闸命令 controlType = 0x1A
        local dltCmd = generateDLT645ControlCommand(meterAddr, 0x1A, password)
        local cmd = generateTransparentForward(commAddr, 1, 30, dltCmd, cmdName)
        return {Status = "0", Variable = cmd}
        
    elseif cmdName == "CloseValve" then
        -- 合闸命令 controlType = 0x1B  
        local dltCmd = generateDLT645ControlCommand(meterAddr, 0x1B, password)
        local cmd = generateTransparentForward(commAddr, 1, 30, dltCmd, cmdName)
        return {Status = "0", Variable = cmd}
        
    elseif cmdName == "login" then
        local cmd = generateLoginCommand(commAddr)
        return {Status = "0", Variable = cmd}
        
    elseif cmdName == "heartbeat" then
        local cmd = generateHeartbeatCommand(commAddr)
        return {Status = "0", Variable = cmd}
        
    elseif cmdName == "logout" then
        local cmd = generateLogoutCommand(commAddr)
        return {Status = "0", Variable = cmd}
        
    elseif cmdName == "transparent" then
        local port = (params and params.port) or 1
        local timeout = (params and params.timeout) or 30
        local data = (params and params.data) or {}
        local cmd = generateTransparentForward(commAddr, port, timeout, data, cmdName)
        return {Status = "0", Variable = cmd}
        
    elseif cmdName == "version" then
        local cmd = generateReadVersionCommand(commAddr)
        return {Status = "0", Variable = cmd}
        
    elseif cmdName == "module_version" then
        local cmd = generateReadModuleVersionCommand(commAddr)
        return {Status = "0", Variable = cmd}
    elseif cmdName == "dev_voltage" then
        -- 添加读取电压的命令 - A相电压 DI = 02 01 01 00
        local dltCmd = generateDLT645ReadCommand(meterAddr, {0x02, 0x01, 0x01, 0x00})
        
        local cmd = generateTransparentForward(commAddr, 1, 30, dltCmd, "dev_voltage")
        return {Status = "0", Variable = cmd}
    elseif cmdName == "dev_read_any" then
        -- 允许用户指定任意数据标识进行读取测试
        -- 参数格式：DI0,DI1,DI2,DI3，例如：00,00,01,00
        local di = {}
        if params and params.dataId then
            -- 解析参数，格式为：00,00,01,00
            for v in string.gmatch(params.dataId, "([^,]+)") do
                table.insert(di, tonumber("0x"..v))
            end
        else
            -- 默认使用正向总有功电能
            di = {0x00, 0x00, 0x01, 0x00}
        end
        
        local dltCmd = generateDLT645ReadCommand(meterAddr, di)
        
        -- 打印生成的DLT645帧内容
        local hexStr = ""
        for i = 1, #dltCmd do
            hexStr = hexStr .. string.format("%02X ", dltCmd[i])
        end
        
        local cmd = generateTransparentForward(commAddr, 1, 30, dltCmd, "dev_read_any")
        return {Status = "0", Variable = cmd}
    end
    
    -- 默认返回心跳命令
    local cmd = generateHeartbeatCommand(commAddr)
    return {Status = "0", Variable = cmd}
end

-- 数据接收分析
function AnalysisRx(sAddr, rxBufCnt)
    if rxBufCnt < 16 then
        rxBuf = {}
        print("错误: 接收数据长度不足，长度: " .. rxBufCnt)
        return {Status = "1", Variable = {}}
    end
    
    local frame, err = parseFrame(rxBuf, rxBufCnt)
    if not frame then
        rxBuf = {}
        print("帧解析错误: " .. err)
        return {Status = "1", Variable = {}}
    end
    
    local variables = {}
    
    -- 解析数据单元标识
    local dataUnit = parseDataUnitID(frame, rxBuf)
    if not dataUnit then
        rxBuf = {}
        print("数据单元解析错误")
        return {Status = "1", Variable = {}}
    end
    
    -- 将DT转换为Fn值进行判断
    local realFn = convertDTToFn(dataUnit.fn)
    
    -- 根据AFN类型解析数据
    if frame.afn == AFN.DATA_FORWARD then
        -- 数据转发响应 - 重点处理DLT645透明转发数据
        if realFn == 1 then
            -- F1：透明转发响应
            local dataStart = dataUnit.dataStart
            
            -- 直接取20字节透明转发内容
            local contentStart = dataStart
            local contentLen = 20
            local contentHex = ""
            for i = contentStart, contentStart + contentLen - 1 do
                contentHex = contentHex .. string.format("%02X ", rxBuf[i] or 0)
            end
            
            -- 在透明转发内容中查找DLT645帧
            local dltStartIndex = -1
            for i = contentStart, contentStart + contentLen - 8 do
                if rxBuf[i] == 0x68 and rxBuf[i+7] == 0x68 then
                    dltStartIndex = i
                    break
                end
            end
            
            if dltStartIndex > 0 then
                -- 打印完整的DLT645帧用于调试
                local frameHex = ""
                for i = dltStartIndex, dltStartIndex + 20 do
                    frameHex = frameHex .. string.format("%02X ", rxBuf[i] or 0)
                end
                
                -- 使用新的解析函数
                local energyValue = parseDLT645EnergyData(rxBuf, dltStartIndex)
                if energyValue then
                    -- 直接使用解析出的值，Go代码会自动除以100
                    local energyVar = utils.AppendVariable(0, "dev_consumption", "总电能", "double", energyValue, string.format("%.2f kwh", energyValue/100))
                    table.insert(variables, energyVar)
                    
                    -- 直接返回解析结果，不再处理其他数据
                    rxBuf = {}
                    return {Status = "0", Variable = variables}
                end
                
                -- 控制码
                local ctrlCode = rxBuf[dltStartIndex + 8] or 0
                
                -- 数据长度
                local dltDataLen = rxBuf[dltStartIndex + 9] or 0
                
                -- 数据标识
                local dataIdStart = dltStartIndex + 10
                local dataId = {}
                for j = 0, 3 do
                    dataId[4-j] = (rxBuf[dataIdStart + j] or 0) - 0x33
                end
                
                -- 检查数据标识是否为正向总有功电能 (00000000)
                if dataId[1] == 0x00 and dataId[2] == 0x01 and dataId[3] == 0x00 and dataId[4] == 0x00 then
                    -- 数据
                    local valueStart = dataIdStart + 4
                    local value = {}
                    for j = 0, 3 do
                        local rawByte = rxBuf[valueStart + j] or 0
                        -- DLT645协议：数据需要减33处理，但要注意溢出处理
                        if rawByte >= 0x33 then
                            value[j+1] = rawByte - 0x33
                        else
                            value[j+1] = rawByte + 256 - 0x33
                        end
                    end
                    local n = ""
                    for i = #value, 1, -1 do
                        n = n.. string.format("%02X", value[i])
                    end
                    
                    -- 根据645-07协议，正向总有功电能格式为 XXXXXX.XX，4字节
                    local consumption = tonumber(n, 10)  -- 使用16进制解析
                    local actualConsumption = consumption 
                    table.insert(variables, utils.AppendVariable(0, "dev_consumption", "正向总有功电能", "double", actualConsumption, "kwh"))
                else
                    -- 尝试解析其他数据标识，支持多种电能数据标识
                    local valueStart = dataIdStart + 4
                    local value = {}
                    for j = 0, 3 do
                        local rawByte = rxBuf[valueStart + j] or 0
                        -- DLT645协议：数据需要减33处理，但要注意溢出处理
                        if rawByte >= 0x33 then
                            value[j+1] = rawByte - 0x33
                        else
                            value[j+1] = rawByte + 256 - 0x33
                        end
                    end
                    
                    local n = ""
                    for i = #value, 1, -1 do
                        n = n.. string.format("%02X", value[i])
                    end
                    
                    -- 检查是否为其他电能数据标识
                    local isEnergyData = false
                    local energyType = ""
                    
                    -- 检查正向有功总电量 (00000000)
                    if dataId[1] == 0x00 and dataId[2] == 0x01 and dataId[3] == 0x00 and dataId[4] == 0x00 then
                        isEnergyData = true
                        energyType = "正向有功总电量"
                    -- 检查组合有功总电量 (04000000)
                    elseif dataId[1] == 0x04 and dataId[2] == 0x00 and dataId[3] == 0x00 and dataId[4] == 0x00 then
                        isEnergyData = true
                        energyType = "组合有功总电量"
                    -- 检查当前剩余电量 (00900100)
                    elseif dataId[1] == 0x00 and dataId[2] == 0x90 and dataId[3] == 0x01 and dataId[4] == 0x00 then
                        isEnergyData = true
                        energyType = "当前剩余电量"
                    end
                    
                    if isEnergyData then
                        -- 使用BCD解码电能数据
                        local actualConsumption = parseBCDEnergyValue(value)
                        table.insert(variables, utils.AppendVariable(0, "dev_consumption", energyType, "double", actualConsumption, string.format("%.2f kWh", actualConsumption)))
                    else
                        -- 未知数据标识，使用BCD解码
                        local actualConsumption = parseBCDEnergyValue(value)
                        table.insert(variables, utils.AppendVariable(0, "dev_consumption", "正向总有功电能", "double", actualConsumption, string.format("%.2f kWh", actualConsumption)))
                    end
                end
            else
                -- 打印当前位置附近的数据
                local debugHex = ""
                for i = contentStart, math.min(contentStart + 10, frame.dataStart + frame.dataLen - 1) do
                    debugHex = debugHex .. string.format("%02X ", rxBuf[i] or 0)
                end
            end
            
        elseif realFn == 254 then
            -- F254：转发表计主动上报数据格式 (保留原有解析逻辑)
            local dataStart = dataUnit.dataStart
            
            -- F254格式：预留(1) + 模块维测数据(8) + 协议类型(1) + 数据长度(2) + 透明转发内容
            local reserved = rxBuf[dataStart] or 0
            dataStart = dataStart + 1
            
            -- 模块维测数据(8字节)
            local moduleData = {}
            for i = 1, 8 do
                moduleData[i] = rxBuf[dataStart + i - 1] or 0
            end
            dataStart = dataStart + 8
            
            local protocol = rxBuf[dataStart] or 0  -- 协议类型
            dataStart = dataStart + 1
            
            local dataLen = (rxBuf[dataStart + 1] or 0) * 256 + (rxBuf[dataStart] or 0)  -- 数据长度(小端序)
            dataStart = dataStart + 2
            
            table.insert(variables, utils.AppendVariable(0, "Reserved", "预留", "Number", reserved, ""))
            table.insert(variables, utils.AppendVariable(1, "Protocol", "协议类型", "Number", protocol, protocol == 0 and "DLT645周期上报" or "其他"))
            table.insert(variables, utils.AppendVariable(2, "DataLen", "数据长度", "Number", dataLen, ""))
            
            -- 解析F254透明转发内容
            if dataLen > 0 and frame.dataLen >= dataStart + dataLen then
                local fdHeader = rxBuf[dataStart] or 0
                if fdHeader == 0xFD then  -- 单表数据头标识
                    dataStart = dataStart + 1
                    
                    -- 解析数据长度 (2字节)
                    local singleDataLen = (rxBuf[dataStart + 1] or 0) * 256 + (rxBuf[dataStart] or 0)
                    dataStart = dataStart + 2
                    
                    -- 解析控制字 (2字节)
                    local controlWord = (rxBuf[dataStart + 1] or 0) * 256 + (rxBuf[dataStart] or 0)
                    dataStart = dataStart + 2
                    
                    -- 解析时间控制字 (2字节)
                    local timeControlWord = (rxBuf[dataStart + 1] or 0) * 256 + (rxBuf[dataStart] or 0)
                    dataStart = dataStart + 2
                    
                    -- 解析TAG个数
                    local tagCount = rxBuf[dataStart] or 0
                    dataStart = dataStart + 1
                    
                    table.insert(variables, utils.AppendVariable(10, "SingleDataLen", "单表数据长度", "Number", singleDataLen, ""))
                    table.insert(variables, utils.AppendVariable(11, "ControlWord", "控制字", "Number", controlWord, string.format("0x%04X", controlWord)))
                    table.insert(variables, utils.AppendVariable(12, "TimeControlWord", "时间控制字", "Number", timeControlWord, string.format("0x%04X", timeControlWord)))
                    table.insert(variables, utils.AppendVariable(13, "TagCount", "TAG个数", "Number", tagCount, ""))
                    
                    -- 解析TAG数据项
                    local tagIndex = 20
                    for i = 1, math.min(tagCount, 50) do  -- 限制最多解析50个TAG避免过长
                        if dataStart + 2 < frame.dataStart + frame.dataLen then
                            local tag = (rxBuf[dataStart] or 0) + (rxBuf[dataStart + 1] or 0) * 256
                            dataStart = dataStart + 2
                            
                            -- 根据TAG解析对应数据
                            local tagData, dataSize = parseTagData(tag, rxBuf, dataStart, frame.dataStart + frame.dataLen)
                            if tagData and dataSize > 0 then
                                table.insert(variables, utils.AppendVariable(tagIndex, "TAG_" .. tag, getTagDescription(tag), "String", tagData, string.format("TAG=0x%04X", tag)))
                                
                                -- 特殊处理：提取电能值到EventData中供Go代码使用
                                if tag == 44 then  -- TAG44: 当前正向有功总电能
                                    local energyBytes = {}
                                    for i = 1, 4 do
                                        energyBytes[i] = rxBuf[dataStart + i - 1] or 0
                                    end
                                    local energyValue = parseBCDEnergyValue(energyBytes)
                                    -- 设置到全局变量中，供Go代码读取
                                    table.insert(variables, utils.AppendVariable(0, "energy_bcd", "BCD电能值", "double", energyValue, string.format("%.2f kWh", energyValue)))
                                    table.insert(variables, utils.AppendVariable(0, "energy", "电能值", "double", energyValue, string.format("%.2f kWh", energyValue)))
                                end
                                
                                dataStart = dataStart + dataSize
                                tagIndex = tagIndex + 1
                            else
                                break  -- 数据不足，结束解析
                            end
                        else
                            break
                        end
                    end
                else
                    -- 非标准单表数据格式，直接显示原始数据
                    local hexData = {}
                    for i = 1, math.min(dataLen, 100) do
                        hexData[i] = rxBuf[dataStart + i - 1] or 0
                    end
                    local hexStr = utils.BytesToHex(hexData)
                    table.insert(variables, utils.AppendVariable(10, "RawData", "原始数据", "String", hexStr, "非标准格式"))
                end
            end
            
        else
            -- 其他数据转发功能码，显示原始数据
            if frame.dataLen > 7 then
                local dataStart = dataUnit.dataStart
                local dataLen = math.min(frame.dataLen - 4, 50)  -- 限制显示长度
                
                local responseData = {}
                for i = 1, dataLen do
                    responseData[i] = rxBuf[dataStart + i - 1] or 0
                end
                local hexStr = utils.BytesToHex(responseData)
                table.insert(variables, utils.AppendVariable(10, "Data", "数据内容", "String", hexStr, ""))
            end
        end
        
    elseif frame.afn == AFN.LINK_TEST then
        -- 链路检测响应
        if dataUnit.fn == 1 then
            table.insert(variables, utils.AppendVariable(0, "LoginStatus", "登录状态", "String", "登录响应", ""))
        elseif dataUnit.fn == 4 then
            table.insert(variables, utils.AppendVariable(0, "HeartbeatStatus", "心跳状态", "String", "心跳响应", ""))
            -- 解析时间数据
            if frame.dataLen > 10 then
                local timeBytes = {}
                for i = 1, 6 do
                    timeBytes[i] = rxBuf[dataUnit.dataStart + i - 1] or 0
                end
                local timeStr = utils.FormatTime(timeBytes)
                table.insert(variables, utils.AppendVariable(0, "DeviceTime", "设备时间", "String", timeStr, ""))
            end
        end
        
    elseif frame.afn == AFN.REQ_CONFIG then
        -- 配置信息响应
        if dataUnit.fn == 1 then  -- F1: 终端版本信息
            if frame.dataLen > 4 then
                local dataStart = dataUnit.dataStart
                -- 尝试解析ASCII字符串
                local version = ""
                for i = 1, math.min(frame.dataLen - 4, 20) do
                    local char = rxBuf[dataStart + i - 1] or 0
                    if char >= 32 and char <= 126 then
                        version = version .. string.char(char)
                    elseif char > 0 then
                        version = version .. string.format("\\x%02X", char)
                    end
                end
                table.insert(variables, utils.AppendVariable(0, "TerminalVersion", "终端版本", "String", version, ""))
            end
        elseif dataUnit.fn == 256 then  -- F9: 通信模块版本信息
            if frame.dataLen > 4 then
                local dataStart = dataUnit.dataStart
                local imei = ""
                for i = 1, math.min(15, frame.dataLen - 4) do
                    local char = rxBuf[dataStart + i - 1] or 0
                    if char >= 32 and char <= 126 then
                        imei = imei .. string.char(char)
                    end
                end
                table.insert(variables, utils.AppendVariable(0, "IMEI", "IMEI号", "String", imei, ""))
            end
        end
        
    elseif frame.afn == AFN.CONFIRM_DENY then
        -- 确认/否认响应
        if dataUnit.fn == 1 then
            table.insert(variables, utils.AppendVariable(0, "Confirm", "确认状态", "String", "全部确认", ""))
        elseif dataUnit.fn == 2 then
            table.insert(variables, utils.AppendVariable(0, "Confirm", "确认状态", "String", "全部否认", ""))
        elseif dataUnit.fn == 4 then
            table.insert(variables, utils.AppendVariable(0, "Confirm", "确认状态", "String", "按数据单元确认/否认", ""))
        end
    end
    
    -- 添加基本帧信息
    table.insert(variables, utils.AppendVariable(100, "AFN", "应用功能码", "Number", frame.afn, string.format("0x%02X", frame.afn)))
    table.insert(variables, utils.AppendVariable(101, "SEQ", "帧序列", "Number", frame.seq % 16, ""))
    table.insert(variables, utils.AppendVariable(102, "Address", "终端地址", "Number", frame.address, ""))
    table.insert(variables, utils.AppendVariable(103, "Fn", "信息类", "Number", dataUnit.fn, string.format("DT=0x%04X", dataUnit.fn)))
    table.insert(variables, utils.AppendVariable(104, "Pn", "信息点", "Number", dataUnit.pn, ""))
    if realFn > 0 then
        table.insert(variables, utils.AppendVariable(105, "RealFn", "实际Fn", "Number", realFn, string.format("F%d", realFn)))
    end
    
    rxBuf = {}
    return {Status = "0", Variable = variables}
end

-- 插件信息函数---------------------------------------------------------------------------------------------------------------

-- 解析设备地址 - 支持两种格式：
-- 格式1: "通讯地址|电表地址" 如 "053040961|202505300001" 
-- 格式2: 单一地址，转换为376.1地址
function parseDeviceAddress(sAddr)
    
    if string.find(sAddr, "|") then
        -- 格式1: 通讯地址|电表地址
        local pos = string.find(sAddr, "|")
        local commAddr = string.sub(sAddr, 1, pos - 1)
        local meterAddr = string.sub(sAddr, pos + 1)
        return commAddr, meterAddr
    else
        local commAddr = convertDlt645To3761(sAddr)
        return commAddr, sAddr
    end
end

-- 生成普通采集任务的命令
function GenerateGetRealVariables(sAddr, step)

    local commAddr, meterAddr = parseDeviceAddress(sAddr)
    
    if step == 0 then
        -- 第一步：读取正向总有功电能
        local dltCmd = generateDLT645ReadCommand(meterAddr, {0x00, 0x00, 0x01, 0x00})
        local cmd = generateTransparentForward(commAddr, 1, 30, dltCmd, "dev_consumption")
        return {Status = "1", Variable = cmd}  -- Status="1" 表示需要发送命令并等待响应
    end
    
    -- 没有更多步骤
    return {Status = "0", Variable = {}}
end

-- 获取支持的命令列表
function GetSupportedCommands()
    return {
        {name = "dev_consumption", desc = "正向总有功电能"},
        {name = "OpenValve", desc = "跳闸操作"},
        {name = "CloseValve", desc = "合闸操作"},
        {name = "login", desc = "登录命令"},
        {name = "heartbeat", desc = "心跳命令"},  
        {name = "logout", desc = "退出登录"},
        {name = "transparent", desc = "透明转发", params = "port,timeout,data"},
        {name = "version", desc = "读取终端版本"},
        {name = "module_version", desc = "读取模块版本"}
    }
end

-- 获取插件信息
function GetPluginInfo()
    return {
        name = "Q/GDW 1376.1",
        version = "1.0.0",
        author = "zdm",
        description = "Q/GDW 1376.1 电力通信协议插件",
        protocol = "Q3761-1376"
    }
end

-- 初始化函数
function Initialize()
    rxBuf = {}
    frameSeq = 0
    print("Q/GDW 1376.1 协议插件已初始化")
    return true
end

-- 清理函数
function Cleanup()
    rxBuf = {}
    print("Q/GDW 1376.1 协议插件已清理")
end

-- TAG数据解析函数
function parseTagData(tag, buffer, startIndex, endIndex)
    local tagInfo = getTagInfo(tag)
    if not tagInfo then
        return nil, 0
    end
    
    local dataSize = tagInfo.size
    if startIndex + dataSize > endIndex then
        return nil, 0  -- 数据不足
    end
    
    local data = {}
    for i = 1, dataSize do
        data[i] = buffer[startIndex + i - 1] or 0
    end
    
    -- 根据TAG类型解析数据
    if tag == 1 then
        -- 表号 (6字节BCD)
        local meterNo = ""
        for i = 6, 1, -1 do  -- 大端序
            meterNo = meterNo .. string.format("%02d", bcdToDec(data[i]))
        end
        return meterNo, dataSize
        
         elseif tag == 2 then
         -- 时间 (7字节): YY MM DD WW hh mm ss
         if dataSize >= 7 then
             local year = bcdToDec(data[1]) + 2000
             local monthByte = data[2] or 0
             local month = bcdToDec(monthByte % 32)  -- 低5位为月份
             local week = math.floor(monthByte / 32)  -- 高3位为星期
             local day = bcdToDec(data[3])
             local hour = bcdToDec(data[4])
             local minute = bcdToDec(data[5])
             local second = bcdToDec(data[6])
             return string.format("%04d-%02d-%02d %02d:%02d:%02d 周%d", year, month, day, hour, minute, second, week), dataSize
         end
        
    elseif tag >= 21 and tag <= 26 then
        -- 电压电流 (2-3字节，除以100)
        local value = 0
        if dataSize == 2 then
            value = data[1] + data[2] * 256
        elseif dataSize == 3 then
            value = data[1] + data[2] * 256 + data[3] * 65536
        end
        if tag <= 23 then
            return string.format("%.1f V", value / 10), dataSize  -- 电压
        else
            return string.format("%.3f A", value / 1000), dataSize  -- 电流
        end
        
    elseif tag >= 27 and tag <= 34 then
        -- 功率 (3字节，单位kW)
        local value = data[1] + data[2] * 256 + data[3] * 65536
        return string.format("%.4f kW", value / 10000), dataSize
        
    elseif tag >= 35 and tag <= 38 then
        -- 功率因数 (2字节，除以1000)
        local value = data[1] + data[2] * 256
        return string.format("%.3f", value / 1000), dataSize
        
    elseif tag >= 39 and tag <= 82 then
        -- 电能 (4字节，单位kWh，除以100)
        local value = data[1] + data[2] * 256 + data[3] * 65536 + data[4] * 16777216
        return string.format("%.2f kWh", value / 100), dataSize
        
    elseif tag == 20 then
        -- 电表运行状态字 (3字节)
        local status = data[1] + data[2] * 256 + data[3] * 65536
        return string.format("0x%06X", status), dataSize
        
    elseif tag >= 44 and tag <= 53 then
        local value = 0
        if dataSize >= 4 then
            -- BCD解码：每字节包含两个十进制数字
            -- 例如：0x17 0x04 0x18 0x90 -> 17041890，表示170418.90 kWh
            local result = 0
            local factor = 0.01  -- 从小数点后2位开始
            
            for i = 1, 4 do  -- 处理4个字节
                local byte = data[i]
                local lowDigit = byte % 16      -- 低4位
                local highDigit = math.floor(byte / 16)  -- 高4位
                
                -- 验证BCD编码有效性
                if lowDigit > 9 or highDigit > 9 then
                    return string.format("BCD解码错误:0x%02X", byte), dataSize
                end
                
                -- BCD解码：低位在前，高位在后
                result = result + lowDigit * factor
                factor = factor * 10
                result = result + highDigit * factor  
                factor = factor * 10
            end
            
            value = result
        end
        
        -- 根据TAG确定电能类型
        local energyType = ""
        if tag == 44 then
            energyType = "正向有功总电能"
        elseif tag == 45 then
            energyType = "正向有功费率1电能"
        elseif tag == 46 then
            energyType = "正向有功费率2电能"
        elseif tag == 47 then
            energyType = "正向有功费率3电能"
        elseif tag == 48 then
            energyType = "正向有功费率4电能"
        elseif tag == 49 then
            energyType = "反向有功总电能"
        elseif tag == 50 then
            energyType = "反向有功费率1电能"
        elseif tag == 51 then
            energyType = "反向有功费率2电能"
        elseif tag == 52 then
            energyType = "反向有功费率3电能"
        elseif tag == 53 then
            energyType = "反向有功费率4电能"
        else
            energyType = "电能数据"
        end
        
        return string.format("%.2f kWh (%s)", value, energyType), dataSize
        
    else
        -- 默认十六进制显示
        local hexStr = ""
        for i = 1, dataSize do
            hexStr = hexStr .. string.format("%02X ", data[i])
        end
        return hexStr:sub(1, -2), dataSize  -- 去掉最后的空格
    end
    
    return nil, 0
end

-- 获取TAG信息
function getTagInfo(tag)
    local tagTable = {
        [1] = {size = 6, desc = "表号"},
        [2] = {size = 7, desc = "时间"},
        [3] = {size = 5, desc = "上次整点冻结时间"},
        [4] = {size = 4, desc = "上次整点冻结正向有功总电能"},
        [5] = {size = 4, desc = "上次整点冻结反向有功总电能"},
        [20] = {size = 3, desc = "电表运行状态字"},
        [21] = {size = 2, desc = "A相电压"},
        [22] = {size = 2, desc = "B相电压"},
        [23] = {size = 2, desc = "C相电压"},
        [24] = {size = 3, desc = "A相电流"},
        [25] = {size = 3, desc = "B相电流"},
        [26] = {size = 3, desc = "C相电流"},
        [27] = {size = 3, desc = "瞬时总有功功率"},
        [28] = {size = 3, desc = "瞬时A相有功功率"},
        [29] = {size = 3, desc = "瞬时B相有功功率"},
        [30] = {size = 3, desc = "瞬时C相有功功率"},
        [31] = {size = 3, desc = "瞬时总视在功率"},
        [32] = {size = 3, desc = "瞬时A相视在功率"},
        [33] = {size = 3, desc = "瞬时B相视在功率"},
        [34] = {size = 3, desc = "瞬时C相视在功率"},
        [35] = {size = 2, desc = "总功率因数"},
        [36] = {size = 2, desc = "A相功率因数"},
        [37] = {size = 2, desc = "B相功率因数"},
        [38] = {size = 2, desc = "C相功率因数"},
        [39] = {size = 4, desc = "当前组合有功总电能"},
        [40] = {size = 4, desc = "当前组合有功费率1电能"},
        [41] = {size = 4, desc = "当前组合有功费率2电能"},
        [42] = {size = 4, desc = "当前组合有功费率3电能"},
        [43] = {size = 4, desc = "当前组合有功费率4电能"},
        [44] = {size = 4, desc = "当前正向有功总电能"},
        [45] = {size = 4, desc = "当前正向有功费率1电能"},
        [46] = {size = 4, desc = "当前正向有功费率2电能"},
        [47] = {size = 4, desc = "当前正向有功费率3电能"},
        [48] = {size = 4, desc = "当前正向有功费率4电能"},
        [49] = {size = 4, desc = "当前反向有功总电能"},
        [73] = {size = 4, desc = "当前组合无功1总电能"},
        [74] = {size = 4, desc = "当前组合无功1费率1电能"},
        [75] = {size = 4, desc = "当前组合无功1费率2电能"},
        [76] = {size = 4, desc = "当前组合无功1费率3电能"},
        [77] = {size = 4, desc = "当前组合无功1费率4电能"},
        [78] = {size = 4, desc = "当前组合无功2总电能"}
    }
    
    return tagTable[tag]
end

-- 获取TAG描述
function getTagDescription(tag)
    local tagInfo = getTagInfo(tag)
    if tagInfo then
        return tagInfo.desc
    else
        return "未知TAG(" .. tag .. ")"
    end
end

-- DLT645 相关函数---------------------------------------------------------------------------------------------------------------

-- DLT645数据加密
function dlt645Encode(source, startIndex, endIndex)
    local y = 13
    for i = startIndex, endIndex do
        local _, dec = math.modf(y / 2)
        if (dec == 0) then
            source[i] = (source[i] + 0x33 + 0x48 + y) % 256
        else
            source[i] = (source[i] + 0x33 + 0x54 + y) % 256
        end
        y = y + 1
    end
end

-- DLT645数据解密
function dlt645Decode(source, startIndex, endIndex)
    local result = {}
    local y = 1
    for i = startIndex, endIndex do
        local _, dec = math.modf(y / 2)
        if (dec == 0) then
            result[y] = source[i] - 0x33 - 0x54 - y + 1
        else
            result[y] = source[i] - 0x33 - 0x48 - y + 1
        end

        if (result[y] < 0) then
            result[y] = result[y] + 256
        end
        y = y + 1
    end
    return result
end

-- 生成DLT645读命令（修正版）
function generateDLT645ReadCommand(meterAddr, dataId)
    -- 使用传入的表地址动态计算DLT645地址域，不再硬编码
    
    -- 处理带|分隔符的地址格式
    local realMeterAddr = meterAddr
    if string.find(meterAddr, "|") then
        -- 如果是"通讯地址|电表地址"格式，提取电表地址部分
        local pos = string.find(meterAddr, "|")
        realMeterAddr = string.sub(meterAddr, pos + 1)
    end
    
    -- 创建基本帧结构
    local dltFrame = {
        0x68,          -- 帧起始符
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  -- 地址域占位符，将根据meterAddr动态计算
        0x68,          -- 帧起始符
        0x11,          -- 控制码：读数据
        0x04,          -- 数据域长度
        0x33, 0x33, 0x34, 0x33,  -- 数据标识+0x33
        0x00,          -- 校验和占位符
        0x16           -- 结束符
    }
    
    -- 地址转换（BCD编码，小端序）- 直接采用generateDLT645ControlCommand中的逻辑
    local addr = string.format("%012d", tonumber(realMeterAddr) or 0)
    
    -- 直接使用与generateDLT645ControlCommand相同的逻辑
    dltFrame[2] = tonumber(addr:sub(11, 12)) or 0  -- 最后两位
    dltFrame[3] = tonumber(addr:sub(9, 10)) or 0   -- 倒数3-4位
    dltFrame[4] = tonumber(addr:sub(7, 8)) or 0    -- 倒数5-6位
    dltFrame[5] = tonumber(addr:sub(5, 6)) or 0    -- 倒数7-8位
    dltFrame[6] = tonumber(addr:sub(3, 4)) or 0    -- 倒数9-10位
    dltFrame[7] = tonumber(addr:sub(1, 2)) or 0    -- 最前两位
    
    -- 转换为BCD格式
    for i = 2, 7 do
        local val = dltFrame[i]
        dltFrame[i] = math.floor(val / 10) * 16 + (val % 10)
    end
    
    -- 如果有传入的数据标识，则使用传入的
    if dataId and #dataId >= 4 then
        for i = 1, 4 do
            dltFrame[10 + i] = (dataId[i] or 0) + 0x33
        end
    end
    
    -- 计算校验和
    dltFrame[15] = checkSum(dltFrame, 1, 14)
    
    -- 在前面插入4个0xFE唤醒符
    for i = 1, 4 do
        table.insert(dltFrame, 1, 0xFE)
    end
    
    return dltFrame
end

-- 生成DLT645控制命令（跳合闸）
function generateDLT645ControlCommand(meterAddr, controlType, password)
    
    -- 处理带|分隔符的地址格式
    local realMeterAddr = meterAddr
    if string.find(meterAddr, "|") then
        -- 如果是"通讯地址|电表地址"格式，提取电表地址部分
        local pos = string.find(meterAddr, "|")
        realMeterAddr = string.sub(meterAddr, pos + 1)
    end
    
    -- 创建基本帧结构
    local dltFrame = {
        0x68,          -- [1] 帧起始符
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  -- [2-7] 地址域占位符
        0x68,          -- [8] 帧头
        0x1C,          -- [9] 控制码(写数据)
        0x10,          -- [10] 数据域长度(16字节)
        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, -- [11-18] 加密后的数据域占位符
        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, -- [19-26] 加密后的数据域占位符
        0x00,          -- [27] 校验和占位符
        0x16           -- [28] 结束符
    }
    
    -- 地址转换（BCD编码，小端序）- 修正逻辑
    -- 对于地址053040961，正确的DLT645地址应该是：01 00 30 05 25 20
    local addr = string.format("%012d", tonumber(realMeterAddr))
    -- DLT645地址是BCD编码，按每2位数字一个字节，小端序排列
    dltFrame[2] = tonumber(addr:sub(11, 12))  -- 最后两位：61 -> 0x01  
    dltFrame[3] = tonumber(addr:sub(9, 10))   -- 倒数3-4位：09 -> 0x00
    dltFrame[4] = tonumber(addr:sub(7, 8))    -- 倒数5-6位：40 -> 0x30 (需要BCD转换)
    dltFrame[5] = tonumber(addr:sub(5, 6))    -- 倒数7-8位：30 -> 0x05 (需要BCD转换)
    dltFrame[6] = tonumber(addr:sub(3, 4))    -- 倒数9-10位：04 -> 0x25 (需要BCD转换)  
    dltFrame[7] = tonumber(addr:sub(1, 2))    -- 最前两位：53 -> 0x20 (需要BCD转换)
    
    -- 转换为BCD格式
    for i = 2, 7 do
        local val = dltFrame[i]
        dltFrame[i] = math.floor(val / 10) * 16 + (val % 10)
    end
    
    -- 准备数据域（加密前）
    local dataField = {}
    
    -- PA: 安全级别02
    table.insert(dataField, 0x02)
    
    -- 密码（7字节，包含6个密码数字+1个保护字节）
    if password and #password >= 6 then
        for i = 1, 6 do
            local digit = tonumber(password:sub(i, i)) or 0
            table.insert(dataField, digit)
        end
        table.insert(dataField, 0)  -- 第7个密码字节（保护字节）
    else
        -- 默认密码 "123456" + 保护字节
        table.insert(dataField, 1)
        table.insert(dataField, 2)
        table.insert(dataField, 3)
        table.insert(dataField, 4)
        table.insert(dataField, 5)
        table.insert(dataField, 6)
        table.insert(dataField, 0)  -- 第7个密码字节（保护字节）
    end
    
    -- 操作类型
    table.insert(dataField, controlType)
    
    -- 保留字节
    table.insert(dataField, 0x00)
    
    -- 时间编码（BCD格式）
    -- 使用正确报文中的时间编码（减去0x33后的原始值）
    local timeData = {
        0x14,  -- 秒 (47-33=14 BCD = 20秒)
        0x44,  -- 分 (77-33=44 BCD = 68分，但这个值有问题，应该是其他含义)  
        0x16,  -- 时 (49-33=16 BCD = 22时)
        0x23,  -- 日 (56-33=23 BCD = 35日，有问题)
        0x06,  -- 月 (39-33=06 BCD = 6月)
        0x25   -- 年 (58-33=25 BCD = 37年，即2037年)
    }
    
    for i = 1, 6 do
        table.insert(dataField, timeData[i])
    end
    
    -- DLT645数据域加密：根据协议标准正确的加密算法
    -- 加密算法：数据+0x33，但密码部分有特殊处理
    for i = 1, #dataField do
        if i == 1 then
            -- PA字段：直接+0x33
            dltFrame[10 + i] = (dataField[i] + 0x33) % 256
        elseif i >= 2 and i <= 8 then
            -- 密码字段：使用特殊编码（参考正确报文的编码方式）- 7个字节
            if i == 2 then dltFrame[10 + i] = 0x89  -- 1的编码
            elseif i == 3 then dltFrame[10 + i] = 0x67  -- 2的编码
            elseif i == 4 then dltFrame[10 + i] = 0x45  -- 3的编码
            elseif i == 5 then dltFrame[10 + i] = 0x33  -- 4的编码
            elseif i == 6 then dltFrame[10 + i] = 0x33  -- 5的编码
            elseif i == 7 then dltFrame[10 + i] = 0x33  -- 6的编码
            elseif i == 8 then dltFrame[10 + i] = 0x33  -- 第7个密码字节(保护字节)的编码
            end
        else
            -- 其他字段：+0x33
            dltFrame[10 + i] = (dataField[i] + 0x33) % 256
        end
    end
    
    -- 计算校验和
    dltFrame[27] = checkSum(dltFrame, 1, 26)
    
    -- 添加4个FE前缀
    for i = 1, 4 do
        table.insert(dltFrame, 1, 0xFE)
    end
    
    return dltFrame
end

-- 解析DLT645电能数据
function parseDLT645EnergyData(data, startIndex)
    -- 检查数据长度
    if #data < startIndex + 10 then
        return nil
    end
    
    -- 检查控制码是否为读数据响应 (0x91)
    local ctrlCode = data[startIndex + 8]
    if ctrlCode ~= 0x91 then
        return nil
    end
    
    -- 数据长度
    local dataLen = data[startIndex + 9]
    
    -- 数据标识（需要减去0x33）
    local dataIdStart = startIndex + 10
    local dataId = {}
    for j = 0, 3 do
        dataId[j+1] = (data[dataIdStart + j] or 0) - 0x33
    end
    
    -- 数据内容（从数据标识后开始）
    local valueStart = dataIdStart + 4
    local valueBytes = {}
    for j = 0, dataLen - 5 do  -- 减去数据标识的4字节和长度字节
        local rawByte = data[valueStart + j] or 0
        -- DLT645协议：数据需要减33处理
        local actualByte = 0
        if rawByte >= 0x33 then
            actualByte = rawByte - 0x33
        else
            actualByte = rawByte + 256 - 0x33
        end
        valueBytes[j+1] = actualByte
    end
    
    -- 调试输出
    local debugHex = ""
    for i = 1, #valueBytes do
        debugHex = debugHex .. string.format("%02X ", valueBytes[i])
    end
    
    -- DLT645 BCD解码：4字节电能数据，小数点后2位
    -- 数据格式：XX XX XX XX，表示XXXXXX.XX kWh
    local result = 0
    if #valueBytes >= 4 then
        -- 从最低位开始解码（小端序）
        for i = 1, 4 do
            local byte = valueBytes[i]
            local lowDigit = byte % 16      -- 低4位
            local highDigit = math.floor(byte / 16)  -- 高4位
            
            -- 确保是有效的BCD数字
            if lowDigit > 9 or highDigit > 9 then
                return nil  -- 出错时返回nil，让调用方处理
            end
            
                         -- BCD解码：每个字节包含2个十进制数字
             -- 第1字节：小数点后2位 (0.XX)
             -- 第2字节：个位和十位 (XX.)
             -- 第3字节：千位和百位
             -- 第4字节：十万位和万位
             if i == 1 then
                 -- 小数点后2位
                 local decimal = (highDigit * 10 + lowDigit) * 0.01
                 result = result + decimal
             elseif i == 2 then
                 -- 个位和十位
                 local ones = (highDigit * 10 + lowDigit) * 1
                 result = result + ones
             elseif i == 3 then
                 -- 百位和千位
                 local hundreds = (highDigit * 10 + lowDigit) * 100
                 result = result + hundreds
             elseif i == 4 then
                 -- 万位和十万位
                 local tenThousands = (highDigit * 10 + lowDigit) * 10000
                 result = result + tenThousands
             end
        end
    else
        print("Q3761-1376: valueBytes长度不足4，跳过BCD解码")
        return nil
    end
    
    -- 适应Go代码的decimal处理逻辑，返回乘以100的整数值
    -- Go代码会除以10^decimal(2) = 100，所以这里先乘以100
    local finalResult = result * 100
    
    return finalResult
end